﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceDownloader.Downloader
{
    internal class PackPerformance
    {
        #region Singleton, konstruktor
        public static PackPerformance Current
        {
            get
            {
                if (_Current == null)
                {
                    lock (_CurrentLock)
                    {
                        if (_Current == null)
                            _Current = PackPerformance.Create();
                    }
                }
                return _Current;
            }
        }
        private static PackPerformance _Current;
        private static object _CurrentLock = new object();
        private PackPerformance()
        {
            this._PackDict = new Dictionary<PackMode, PackModeData>();
            this._PerformFile = System.IO.Path.Combine(Steward.MainExePath, "TraceDownloader.performance.xml");
            this.InitTimer();
        }
        private static PackPerformance Create()
        {
            PackPerformance pp = new PackPerformance();
            IEnumerable<PackInfo> packInfos = PackInfo.ModeList;
            foreach (PackInfo packInfo in packInfos)
                pp._PackDict.Add(packInfo.PackMode, new PackModeData(packInfo));
            pp.PerformanceLoad();
            return pp;
        }
        private Dictionary<PackMode, PackModeData> _PackDict;
        private string _PerformFile;
        #endregion
        #region Add, Get, Clear data
        internal static void AddRealData(PackMode packMode, long sourceLength, long packLength, double packSeconds)
        {
            PackModeData data = Current.GetData(packMode);
            data.AddRealData(sourceLength, packLength, packSeconds);
            Current.Autosave();
        }
        internal static void AddSampleData(PackMode packMode, long sourceLength, long packLength, double packSeconds)
        {
            PackModeData data = Current.GetData(packMode);
            data.AddSampleData(sourceLength, packLength, packSeconds);
            Current.Autosave();
        }
        internal static void Clear()
        {
            Current.ClearData();
            Current.Autosave();
        }
        internal static decimal GetSpeedMBs(PackMode packMode)
        {
            PackModeData data = Current.GetData(packMode);
            return data.SpeedMBs;
        }
        internal static decimal GetCompressionRatio(PackMode packMode)
        {
            PackModeData data = Current.GetData(packMode);
            return data.CompressionRatio;
        }
        #endregion
        #region Lazy AutoSave
        private void Autosave()
        {
            this.Timer.Interval = AutoSaveDelay;
            this.Timer.Enabled = true;
            this.Timer.Start();
        }
        private void InitTimer()
        {
            this.Timer = new System.Timers.Timer(AutoSaveDelay);
            this.Timer.Enabled = false;
            this.Timer.AutoReset = false;
            this.Timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
        }
        void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.Timer.Stop();
            this.Timer.Enabled = false;
            this.PerformanceSave();
        }
        private const double AutoSaveDelay = 3500d;
        private System.Timers.Timer Timer;
        #endregion
        #region Serializace
        protected void PerformanceLoad()
        {
            string file = this._PerformFile;
            if (!System.IO.File.Exists(file)) return;

            lock (this._PackDict)
            {
                this.ClearData();

                XDocument xDoc = XDocument.Load(file);
                XElement xRoot = xDoc.Root;
                if (xRoot.Name.LocalName == "performance")
                {
                    XElement xModes = xRoot.Element("mode_list");
                    if (xModes != null)
                    {
                        foreach (XElement xMode in xModes.Elements())
                        {
                            PackMode packMode = PackModeData.GetPackModeFromSerial(xMode);
                            PackModeData data = this.GetData(packMode);
                            data.Serial = xMode;
                        }
                    }
                }
            }
        }
        protected void PerformanceSave()
        {
            lock (this._PackDict)
            {
                XDocument xDoc = new XDocument();

                XElement xRoot = new XElement("performance");
                xRoot.AddAttribute("version", "1.0");
                xDoc.Add(xRoot);

                XElement xModes = new XElement("mode_list");
                xRoot.Add(xModes);

                foreach (PackModeData mode in this._PackDict.Values)
                {
                    if (!mode.IsEmpty)
                        xModes.Add(mode.Serial);
                }

                xDoc.Save(this._PerformFile, true);
            }
        }
        protected void ClearData()
        {
            foreach (PackModeData data in this._PackDict.Values)
                data.Clear();
        }
        #endregion
        #region Protected metody: GetData
        protected PackModeData GetData(PackMode packMode)
        {
            PackModeData data;
            if (this._PackDict.TryGetValue(packMode, out data))
                return data;
            throw new InvalidOperationException("Neexistují výkonnostní data pro režim " + packMode.ToString());
        }
        #endregion
        #region class PackModeData : položka za jeden režim pakování (obsahuje pevná data, sample data a seznam reálných dat)
        /// <summary>
        /// PackModeData : položka za jeden režim pakování (obsahuje pevná data, sample data a seznam reálných dat)
        /// </summary>
        protected class PackModeData
        {
            #region Konstrukce, základní property
            /// <summary>
            /// Konstruktor pro konkrétní režim
            /// </summary>
            /// <param name="packInfo"></param>
            public PackModeData(PackInfo packInfo)
            {
                this.PackMode = packInfo.PackMode;
                this.PresetData = new PerformanceData(packInfo.PresetSpeedMBs, packInfo.PresetCompressionRatio);
                this.SampleData = null;
                this.RealDataList = new List<PerformanceData>();
                this.RealDataMaxCount = 60;
            }
            public override string ToString()
            {
                return "Data " + this.PackMode.ToString() + ": Preset: " + this.PresetData.ToString() + "; RealCount: " + this.RealDataList.Count.ToString();
            }
            internal PackMode PackMode { get; private set; }
            internal PerformanceData PresetData { get; private set; }
            internal PerformanceData SampleData { get; private set; }
            internal List<PerformanceData> RealDataList { get; private set; }
            internal int RealDataMaxCount { get; private set; }
            internal bool IsEmpty { get { return (this.SampleData == null && this.RealDataList.Count == 0); } }
            #endregion
            #region Result property (SpeedMBs, 
            /// <summary>
            /// Rychlost v MB/sec.
            /// Pokud existuje 3 a více reálných dat, vrací se jejich průměr. Jinak se vrací Sample nebo Preset.
            /// </summary>
            internal decimal SpeedMBs
            {
                get
                {
                    if (this.RealDataList.Count >= 3)
                        return this.RealDataList.AverageQ(data => data.SpeedMBs);
                    if (this.SampleData != null)
                        return this.SampleData.SpeedMBs;
                    return this.PresetData.SpeedMBs;
                }
            }
            /// <summary>
            /// Komprimační poměr vyjadřující velikost pakovanou / velikost zdrojová, například 0.10 (zipuje se na 10% původní velikosti)
            /// Pokud existuje 3 a více reálných dat, vrací se jejich průměr. Jinak se vrací Sample nebo Preset.
            /// </summary>
            internal decimal CompressionRatio
            {
                get
                {
                    if (this.RealDataList.Count >= 3)
                        return this.RealDataList.AverageQ(data => data.CompressionRatio);
                    if (this.SampleData != null)
                        return this.SampleData.CompressionRatio;
                    return this.PresetData.CompressionRatio;
                }
            }
            #endregion
            #region Serializace
            /// <summary>
            /// Element, který představuje serializovaná data instance
            /// </summary>
            public XElement Serial
            {
                get { return this._GetSerial(); }
                set { this._SetSerial(value); }
            }
            /// <summary>
            /// Přečte a vrátí režim PackMode z daného elementu
            /// </summary>
            /// <param name="xMode"></param>
            /// <returns></returns>
            public static PackMode GetPackModeFromSerial(XElement xMode)
            {
                if (xMode == null || xMode.Name.LocalName != "mode")
                    return PackMode.None;

                return Convertor.StringToEnum<PackMode>(xMode.GetAttributeString("PackMode"), Downloader.PackMode.None);
            }
            private XElement _GetSerial()
            {
                XElement xMode = new XElement("mode");
                xMode.AddAttribute("PackMode", Convertor.EnumToString<PackMode>(this.PackMode));

                if (this.SampleData != null)
                {
                    XElement xSampleData = new XElement("sample_data");
                    xSampleData.Add(this.SampleData.Serial);
                    xMode.Add(xSampleData);
                }

                if (this.RealDataList.Count > 0)
                {
                    XElement realList = new XElement("real_list");
                    foreach (PerformanceData data in this.RealDataList)
                        realList.Add(data.Serial);
                    xMode.Add(realList);
                }

                return xMode;
            }
            private void _SetSerial(XElement xMode)
            {
                this.Clear();
                PackMode packMode = GetPackModeFromSerial(xMode);
                if (packMode != this.PackMode)
                    throw new InvalidOperationException("Nelze provést deserializaci dat typu " + packMode.ToString() + " do objektu typu " + this.PackMode.ToString() + ".");

                XElement xSampleData = xMode.Element("sample_data");
                if (xSampleData != null)
                {
                    XElement xData = xSampleData.Elements().FirstOrDefault();
                    this.SampleData = PerformanceData.CreateFrom(xData);
                }

                XElement xRealList = xMode.Element("real_list");
                if (xRealList != null)
                {
                    foreach (XElement xData in xRealList.Elements())
                    {
                        PerformanceData data = PerformanceData.CreateFrom(xData);
                        if (data != null)
                            this.RealDataList.Add(data);
                    }
                }
            }
            #endregion
            #region Add, Get, Clear
            internal void AddRealData(long sourceLength, long packLength, double packSeconds)
            {
                if (packSeconds <= 0.25d || sourceLength <= 32768m) return;           // Malé soubory (velice rychlé a pod 32KB) nebudu brát do reálných statistik.

                int removeCount = this.RealDataList.Count - this.RealDataMaxCount;
                if (removeCount > 0)
                    this.RealDataList.RemoveRange(0, removeCount);

                this.RealDataList.Add(new PerformanceData(sourceLength, packLength, packSeconds));
            }
            internal void AddSampleData(long sourceLength, long packLength, double packSeconds)
            {
                if (packSeconds <= 0d || sourceLength <= 0m) return;
                this.SampleData = new PerformanceData(sourceLength, packLength, packSeconds);
            }
            internal void Clear()
            {
                this.SampleData = null;
                this.RealDataList.Clear();
            }
            #endregion
        }
        #endregion
        #region class PerformanceData : položka obsahující výkony jednoho běhu
        /// <summary>
        /// PerformanceData : položka obsahující výkony jednoho běhu
        /// </summary>
        protected class PerformanceData
        {
            private PerformanceData() { }
            public static PerformanceData CreateFrom(XElement serial)
            {
                if (serial == null) return null;
                PerformanceData data = new PerformanceData();
                data.Serial = serial;
                return (data.IsEmpty ? (PerformanceData)null : data);
            }
            public PerformanceData(decimal speedMBs, decimal compressionRatio)
            {
                this.SpeedMBs = speedMBs;
                this.CompressionRatio = compressionRatio;
            }
            public PerformanceData(long sourceLength, long packLength, double packSeconds)
            {
                if (packSeconds > 0d && sourceLength > 0m)
                {
                    decimal speed = (decimal)sourceLength / (decimal)packSeconds;
                    this.SpeedMBs = speed / 1048576m;
                    this.CompressionRatio = (decimal)packLength / (decimal)sourceLength;
                }
            }
            public override string ToString()
            {
                return "Speed: " + this.SpeedMBs.ToString("# ##0.0").Trim() + " MB/s;  Compression: " + (100 * this.CompressionRatio).ToString("##0.00") + " %";
            }
            /// <summary>
            /// Rychlost v MB/sec
            /// </summary>
            internal decimal SpeedMBs { get; private set; }
            /// <summary>
            /// Komprimační poměr vyjadřující velikost pakovanou / velikost zdrojová, například 0.10 (zipuje se na 10% původní velikosti)
            /// </summary>
            internal decimal CompressionRatio { get; private set; }
            /// <summary>
            /// Je prázdný?
            /// </summary>
            internal bool IsEmpty { get { return (this.SpeedMBs == 0m && this.CompressionRatio == 0m); } }
            #region Serializace
            /// <summary>
            /// Element, který představuje serializovaná data instance
            /// </summary>
            public XElement Serial
            {
                get { return this._GetSerial(); }
                set { this._SetSerial(value); }
            }
            private XElement _GetSerial()
            {
                XElement element = new XElement("data");
                element.AddAttribute("SpeedMBs", this.SpeedMBs);
                element.AddAttribute("CompressionRatio", 100m * this.CompressionRatio);
                return element;
            }
            private void _SetSerial(XElement element)
            {
                this.Clear();
                if (element == null || element.Name.LocalName != "data")
                    return;

                this.SpeedMBs = element.GetAttributeDecimal("SpeedMBs");
                this.CompressionRatio = element.GetAttributeDecimal("CompressionRatio") / 100m;
            }
            internal void Clear()
            {
                this.SpeedMBs = 0m;
                this.CompressionRatio = 0m;
            }
            #endregion
        }
        #endregion
    }
}
